require "test_helper"

class Notification::BundleTest < ActiveSupport::TestCase
  setup do
    @user = users(:david)
    @user.settings.bundle_email_every_few_hours!
  end

  test "new notifications are bundled" do
    notification = assert_difference -> { @user.notification_bundles.pending.count }, 1 do
      @user.notifications.create!(source: events(:logo_published), creator: @user)
    end

    bundle = @user.notification_bundles.pending.last
    assert_includes bundle.notifications, notification
  end

  test "don't bundle new notifications if bundling is disabled" do
    @user.settings.bundle_email_never!

    assert_no_difference -> { @user.notification_bundles.count } do
      @user.notifications.create!(source: events(:logo_published), creator: @user)
    end
  end

  test "notifications are bundled withing the aggregation period" do
    @user.notification_bundles.destroy_all

    notification_1 = assert_difference -> { @user.notification_bundles.pending.count }, 1 do
      @user.notifications.create!(source: events(:logo_published), creator: @user)
    end
    travel_to 3.hours.from_now

    notification_2 = assert_no_difference -> { @user.notification_bundles.count } do
      @user.notifications.create!(source: events(:logo_published), creator: @user)
    end
    travel_to 3.days.from_now

    notification_3 = assert_difference -> { @user.notification_bundles.pending.count }, 1 do
      @user.notifications.create!(source: events(:logo_published), creator: @user)
    end

    assert_equal 2, @user.notification_bundles.count
    bundle_1, bundle_2 = @user.notification_bundles.all.to_a
    assert_includes bundle_1.notifications, notification_1
    assert_includes bundle_1.notifications, notification_2
    assert_includes bundle_2.notifications, notification_3
  end

  test "overlapping bundles are invalid" do
    bundle_1 = @user.notification_bundles.create!(
      starts_at: Time.current,
      ends_at: 4.hours.from_now,
      status: :pending
    )

    bundle_2 = @user.notification_bundles.build(
      starts_at: 2.hours.from_now,
      ends_at: 6.hours.from_now,
      status: :pending
    )
    assert_not bundle_2.valid?

    # Bundle with overlapping end time should be invalid
    bundle_3 = @user.notification_bundles.build(
      starts_at: 2.hours.ago,
      ends_at: 2.hours.from_now,
      status: :pending
    )
    assert_not bundle_3.valid?

    # Bundle completely within another bundle should be invalid
    bundle_4 = @user.notification_bundles.build(
      starts_at: 1.hour.from_now,
      ends_at: 3.hours.from_now,
      status: :pending
    )
    assert_not bundle_4.valid?

    # Non-overlapping bundle should be valid
    bundle_5 = @user.notification_bundles.build(
      starts_at: 5.hours.from_now,
      ends_at: 9.hours.from_now,
      status: :pending
    )
    assert bundle_5.valid?
  end

  test "overlapping bundles that are created relying on set_default_window are not created" do
    @user.notification_bundles.destroy_all

    bundle = @user.notification_bundles.create!(starts_at: Time.current)

    assert_raises ActiveRecord::RecordInvalid do
      @user.notification_bundles.create!(starts_at: bundle.starts_at - 1.second)
    end
  end

  test "deliver_all delivers due bundles" do
    @user.notification_bundles.destroy_all

    notification = @user.notifications.create!(source: events(:logo_published), creator: @user)

    bundle = @user.notification_bundles.pending.last

    assert bundle.pending?
    assert_includes bundle.notifications, notification

    bundle.update!(ends_at: 1.minute.ago)

    perform_enqueued_jobs only: Notification::Bundle::DeliverJob do
      Notification::Bundle.deliver_all
    end

    bundle.reload
    assert bundle.delivered?
  end

  test "deliver_all don't deliver bundles that are not due" do
    @user.notifications.create!(source: events(:logo_published), creator: @user)
    bundle = @user.notification_bundles.pending.last

    bundle.update!(ends_at: 1.minute.from_now)

    perform_enqueued_jobs only: Notification::Bundle::DeliverJob do
      Notification::Bundle.deliver_all
    end

    bundle.reload
    assert bundle.pending?
  end

  test "deliver sends email with time in user's time zone" do
    @user.settings.update!(timezone_name: "Madrid")

    freeze_time Time.utc(2025, 1, 15, 14, 30, 0) do
      @user.notifications.create!(source: events(:logo_published), creator: @user)
      bundle = @user.notification_bundles.pending.last
      bundle.deliver

      email = ActionMailer::Base.deliveries.last
      assert_not_nil email

      # Time in Madrid should be 15:30 (UTC+1 in winter)
      assert_match /notifications since 3pm/i, email.text_part&.body&.to_s
    end
  end

  test "out-of-order notification bundling should still work" do
    first_notification = @user.notifications.create!(source: events(:logo_published), creator: @user)
    second_notification = @user.notifications.create!(source: events(:logo_published), creator: @user)
    @user.notification_bundles.destroy_all

    assert first_notification.created_at < second_notification.created_at
    @user.bundle(second_notification)
    @user.bundle(first_notification)

    assert_equal 1, @user.notification_bundles.pending.count
    assert_equal 2, @user.notification_bundles.last.notifications.count
    assert_includes @user.notification_bundles.last.notifications, first_notification
    assert_includes @user.notification_bundles.last.notifications, second_notification
  end
end
